package cn.car.controller;

import cn.car.domains.entity.CameraInfoEntity;
import cn.car.exception.CarAccessControlException;
import cn.car.service.CameraService;
import cn.car.service.config.SystemConfig;
import cn.car.service.remote.TjRemoteService;
import cn.car.utils.CarStringTemplateUtil;
import cn.car.utils.NvrUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;

@RestController
@Slf4j
public class StreamController {

    @Resource
    private SystemConfig systemConfig;

    @Resource
    private TjRemoteService tjRemoteService;


    @Resource
    private CameraService cameraService;

    @Resource
    private NvrUtil nvrUtil;


    @GetMapping("/api/startStream/{cameraCode}")
    public void startStream(HttpServletResponse response
            , @RequestParam(value = "access_token", required = false) String accessToken
            , @RequestParam(value = "starttime", required = false) String startTime
            , @RequestParam(value = "endtime", required = false) String endTime
            , @RequestParam(value = "debug", required = false) String debug
            , @PathVariable String cameraCode) {

        Process ffmpegProcess;
        try {
            log.info("[startStream] start accessToken:{}, starttime:{}, endtime:{}, debug:{}", accessToken, startTime, endTime, debug);
            CameraInfoEntity cameraInfo = cameraService.queryByCode(cameraCode);
            if (cameraInfo == null) {
                log.error("[StreamController startStream] cameraInfo is null cameraCode:{}", cameraCode);
                throw CarAccessControlException.PARAMS_ERROR;
            }
            String nvrAddressUrl = cameraInfo.getNvrAddressUrl();
            String channelCode = cameraInfo.getChannelCode();

            int between = checkStartStreamParam(debug, accessToken, startTime, endTime);

            // 设置内容类型为 FLV
            response.setContentType("video/x-flv");
            response.setHeader("Access-Control-Allow-Origin", "*");
            // FFmpeg 命令
            //String command = String.format("ffmpeg -rtsp_transport tcp -i %s -c:v copy -c:a aac -t 10 -f flv -loglevel quiet pipe:1", "rtsp://admin:tc123456@192.168.2.153/Streaming/tracks/201?starttime=20241104T215300Z&endtime=20241104T215310Z");

            final String nvrRtspUrl = nvrUtil.getNvrRtspUrl(startTime, endTime, nvrAddressUrl, channelCode);
            final String command = getFFmpegCommand(between, nvrRtspUrl);


            log.info("[startStream] command:{}", command);
            ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
            processBuilder.redirectErrorStream(true);
            ffmpegProcess = processBuilder.start();

            // 获取 FFmpeg 的输出流
            try (InputStream ffmpegOutput = ffmpegProcess.getInputStream();
                 OutputStream responseOutput = response.getOutputStream()) {

                byte[] buffer = new byte[4096];
                int bytesRead;
                log.info("[startStream] start read");
                // 将 FFmpeg 输出流写入 HTTP 响应
                while ((bytesRead = ffmpegOutput.read(buffer)) != -1) {
                    responseOutput.write(buffer, 0, bytesRead);
                    responseOutput.flush();
                }

            } catch (AsyncRequestNotUsableException e) {
                log.info("[startStream] ffmpeg AsyncRequestNotUsableException.", e);
            } catch (Exception e) {
                log.error("[startStream] ffmpeg error.", e);
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            } finally {
                try {
                    ffmpegProcess.waitFor();
                } catch (InterruptedException e) {
                    log.error("[startStream InterruptedException] error");
                } finally {
                    ffmpegProcess.destroy();
                }
            }
            log.info("[startStream] read end");
        } catch (CarAccessControlException e) {
            throw e;
        } catch (Exception e) {
            log.error("[startStream] unknown error.", e);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    private String getFFmpegCommand(int between, String rtspUrl) {
        final JSONObject vars = new JSONObject()
                .fluentPut("between", between)
                .fluentPut("rtspUrl", rtspUrl);
        final String template = between == 0 ?
                "ffmpeg -rtsp_transport tcp -i \"${rtspUrl}\" -c:v copy -an -f flv -loglevel quiet pipe:1"
                : "ffmpeg -rtsp_transport tcp -i \"${rtspUrl}\" -c:v copy -an -t ${between} -f flv -loglevel quiet pipe:1";
        final String command = CarStringTemplateUtil.BRACES.replace(template, vars);
        log.info("[getFFmpegCommand] template:{}, command:{}, vars:{}", template, command, vars);
        return command;
    }




    private int checkStartStreamParam(String reqDebug, String accessToken, String startTime, String endTime) {

        Boolean systemDebug = systemConfig.getTestStream().get();
        if (!(systemDebug || Boolean.parseBoolean(reqDebug))) {
            String localAccessToken = tjRemoteService.getLocalAccessToken();
            if (null == accessToken || (!accessToken.equals(localAccessToken) && !"INNER_TOKEN".equals(accessToken))) {
                log.info("[StreamController checkStartStreamParam] accessToken error : {}", accessToken);
                throw CarAccessControlException.PARAMS_ERROR;
            }
        }


        if ((StrUtil.isEmpty(startTime) && StrUtil.isNotEmpty(endTime))
                || (StrUtil.isNotEmpty(startTime) && StrUtil.isEmpty(endTime))) {
            log.error("[StreamController checkStartStreamParam]  time error startTime:{} endTime:{}", startTime, endTime);
            throw CarAccessControlException.PARAMS_ERROR;
        }

        if (StrUtil.isNotEmpty(startTime)) {

            if (startTime.equals(endTime)) {
                log.error("[StreamController checkStartStreamParam] startTime == endTime startTime:{} endTime:{}", startTime, endTime);
                throw CarAccessControlException.PARAMS_ERROR;
            }

            Date startDate = DateUtil.parse(startTime.replace("t", "").replace("z", ""),
                    DatePattern.PURE_DATETIME_PATTERN);

            Date endDate = DateUtil.parse(endTime.replace("t", "").replace("z", ""),
                    DatePattern.PURE_DATETIME_PATTERN);

            int between = (int) DateUtil.between(startDate, endDate, DateUnit.SECOND);
            log.info("[StreamController checkStartStreamParam] startTime endTime between :{}", between);
            return between;
        }
        return 0;
    }
}
